package com.xuecheng.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @description 安全管理配置
 * todo
 */
//@EnableWebSecurity 负责URL 级别的安全控制（如哪些路径需要登录、哪些路径允许匿名访问）。
//    @EnableGlobalMethodSecurity 负责方法级别的安全控制（如某个 Service 方法只能被特定角色调用）。
@EnableWebSecurity//开启springSecurity
//ecuredEnabled = true：启用@Secured注解，用于声明方法需要特定的角色才能访问。
//prePostEnabled = true：启用@PreAuthorize和@PostAuthorize注解，支持更灵活的表达式式权限控制。
//@PreAuthorize：在方法执行前验证权限（常用），例如：@PreAuthorize("hasRole('ADMIN') or authentication.name == #username")
//表示 “拥有 ADMIN 角色，或用户名与方法参数中的 username 一致时才能访问”。
//@PostAuthorize：在方法执行后验证权限（较少用，通常用于根据返回值判断权限）。
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)//开启方法级别的安全控制（方法授权），允许在单个方法上定义访问权限规则
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
// DaoAuthenticationProvider调用UserDetailsService的loadUserByUsername()方法获取UserDetails用户信息。
// 而不同的认证方式提交的数据不一样，比如：手机加验证码方式会提交手机号和验证码，账号密码方式会提交账号、密码、验证码。
// 我们可以在loadUserByUsername()方法上作文章，将用户原来提交的账号数据改为提交json数据，json数据可以扩展不同认证方式所提交的各种参数。
// AuthParamsDto 就是这样作用
    @Autowired//DaoAuthenticationProvider
    DaoAuthenticationProviderCustom daoAuthenticationProviderCustom;

    @Override//UserDetailsService也在里面配置了
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(daoAuthenticationProviderCustom);
    }
//上面是修改WebSecurityConfig类指定daoAuthenticationProviderCustom

    @Bean//5版本的配置方法,实现自动注入Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    //配置用户信息服务 todo springSecurity验证密码的重要一环,后面重写了所以注释
//    @Bean//UserDetailsService是一个接口,InMemoryUserDetailsManager是其实现类
//    public UserDetailsService userDetailsService() {
        //这里配置用户信息,这里暂时使用这种方式将用户存储在内存中
//        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
//        manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
//        return manager;
//    }

    @Bean//配置密码编码器（PasswordEncoder），主要用于处理用户密码的加密与验证
    public PasswordEncoder passwordEncoder() {
        //这是一个特殊的编码器，不对密码进行任何加密处理，直接以明文形式存储和验证
//        return NoOpPasswordEncoder.getInstance();//单例模式，返回唯一实例。
        return new BCryptPasswordEncoder(); //基于 BCrypt 算法的加密器，会自动生成随机盐值，对密码进行不可逆加密。
        //密码加密只在用户注册或修改密码时执行一次
    }

//练习 BCryptPasswordEncoder是PasswordEncoder实现类
    public static void main(String[] args) {
        String password = "111111";
//        class BCryptPasswordEncoder implements PasswordEncoder
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        for (int i = 0; i < 5; i++) {
//            生成密码
            String encode = passwordEncoder.encode(password);
            System.out.println(encode);
//            校验密码
            boolean matches = passwordEncoder.matches(password, encode);
            System.out.println(matches);
        }
        boolean b1 = passwordEncoder.matches("111111", "$2a$10$hfNx/ws7dOCNZlu6MTbtb.7t0F05i5TQ2cz/0f9N6t3wTSFWjAR1m");
        boolean b2 = passwordEncoder.matches("111111", "$2a$10$RJpKcLkNLgmsgtvEK94UzuEJJTw8Clioe2VdH6uOxb4cxj2r.Tqhy");
        System.out.println(b1 + ":" + b2);
    }
    //配置安全拦截机制,配置 HTTP 请求安全拦截规则的核心配置.定义了系统对不同请求的安全策略
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests() //开启请求授权配置,后续的.antMatchers()、.anyRequest()等方法都是基于它来定义规则。
//  antMatchers("/r/**")使用 Ant 风格的路径匹配，**表示匹配任意层级的路径。  authenticated()表示该路径需要认证通过（用户登录且身份合法）
                .antMatchers("/r/**").authenticated()//访问/r开始的请求需要认证通过
                .anyRequest().permitAll()//其它请求全部放行 anyRequest()必须放在所有antMatchers()之后，因为 Spring Security 的匹配规则是 “先定义的优先”，如果放前面会覆盖后面的具体规则。
//  .formLogin()启用默认的表单登录页面（Spring Security 提供的默认登录页），用户未登录时访问/r/**路径会被自动重定向到该登录页。
//  successForwardUrl("/login-success")表示登录成功后，系统会转发（forward）到/login-success路径（通常是一个登录成功后的处理接口或页面）。
//  与defaultSuccessUrl()的区别：successForwardUrl是强制跳转，而defaultSuccessUrl会优先跳转到登录前的目标路径（如访问/r/user被拦截登录，成功后会跳回/r/user）。
                .and() // 从授权配置切换回 HttpSecurity 对象
                .formLogin().successForwardUrl("/login-success");//登录成功跳转到/login-success
    }
}
//潜在扩展点
//可以添加登录失败处理：.formLogin()
//    .successForwardUrl("/login-success")
//    .failureForwardUrl("/login-fail") // 登录失败跳转

//可以自定义登录页面（替换默认的登录页）:.formLogin()
//    .loginPage("/custom-login") // 自定义登录页路径
//    .loginProcessingUrl("/do-login") // 处理登录请求的接口

//可以添加退出登录配置：.and().logout()
//    .logoutUrl("/logout") // 退出登录接口
//    .logoutSuccessUrl("/login") // 退出成功后跳转页
